/*
 *   Copyright 2006 Wei Lian <lianwei2003.student@sina.com>
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public License
 *   as published by the Free Software Foundation; either version 2
 *   of the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/ptrace.h>
#include <sys/wait.h>
//#include <linux/user.h>
#include <linux/ptrace.h>
#include <sys/syscall.h>
#include <dirent.h>

/////////
#include <qapplication.h>
#include <qtabwidget.h>
#include <qvbox.h>
#include <qhbox.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qstatusbar.h>
#include <qprocess.h>
#include <qlineedit.h>
#include <qsettings.h>
#include <qslider.h>
#include <qtimer.h>
#include <qtextedit.h>
#include <qthread.h>

#include <qtextcodec.h>
////class
class Feidian:public QTabWidget, QThread
{
  Q_OBJECT			////
public:
  Feidian (QWidget * parent = 0, const char *name = 0);
  virtual void run ();

private:			////
    QTextEdit * textstream;
  QStatusBar *bartip;
  QProcess *player;
  QPushButton *buttonplayer;
  QSlider *slidersound;
  QLineEdit *editplayer;
  QTextCodec *g_pCodec;
  pid_t traced_process;
  char newurl[128];
  char oldurl[128];
  void forkplayer (char *);
  long find_pid_by_name (char *, char *);
  void killplayer ();
  void getdata (pid_t, long, char *, int);
  int sound_read ();
  int sound_handle;


  private slots:		////
  void onbuttonattachclicked ();
  void onbuttondetachclicked ();
  void onbuttonplayerclicked ();
  void onbuttonsaveclicked ();
  void onbuttonquitclicked ();
  void timerDone ();
  void onplayerexit ();
  void sound_write (int);
};


Feidian::Feidian (QWidget * parent, const char *name):
QTabWidget (parent, name)
{
  QVBox *tab = new QVBox (this);
  QSettings settings;
  g_pCodec = QTextCodec::codecForName ("GB18030");

  QHBox *hbox = new QHBox (tab);
  QLabel *label;
  label = new QLabel ("Player", hbox);
  editplayer =
    new QLineEdit (settings.
		   readEntry ("/feidian/player",
			      "mplayer -ontop -geometry 100%:100%"), hbox);

  hbox = new QHBox (tab);
  QPushButton *buttonattach = new QPushButton ("Attach",
					       hbox);
  QPushButton *buttondetach = new QPushButton ("Detach",
					       hbox);
  buttonplayer = new QPushButton ("Player", hbox);

  if ((sound_handle = open ("/dev/mixer", O_RDWR, 0)) == -1)
    {
      perror ("/dev/mixer");
      return;
    }
  slidersound =
    new QSlider (0, 100, 10, sound_read (), QSlider::Horizontal, hbox);

  QPushButton *buttonsave = new QPushButton ("Save",
					     hbox);

  textstream = new QTextEdit (tab);
  bartip = new QStatusBar (tab);

  addTab (tab, "Feidian-monitor");

  ////signals and slots
  connect (slidersound, SIGNAL (valueChanged (int)),
	   SLOT (sound_write (int)));
  connect
    (buttonattach,
     SIGNAL (clicked ()), this, SLOT (onbuttonattachclicked ()));
  connect
    (buttondetach,
     SIGNAL (clicked ()), this, SLOT (onbuttondetachclicked ()));
  connect
    (buttonplayer,
     SIGNAL (clicked ()), this, SLOT (onbuttonplayerclicked ()));
  connect (buttonsave, SIGNAL (clicked ()), this,
	   SLOT (onbuttonsaveclicked ()));

  player = new QProcess (this);
  connect (player, SIGNAL (processExited ()), this, SLOT (onplayerexit ()));

  QTimer *timer = new QTimer (this);
  connect (timer, SIGNAL (timeout ()), this, SLOT (timerDone ()));

  ////initial value
  timer->start (1000, FALSE);
  strcpy (newurl, " ");
  strcpy (oldurl, " ");

}

void
Feidian::timerDone ()
{
  ////sound volume
  int value = sound_read ();
  if (slidersound->value () != value)
    slidersound->setValue (value);
  ////player
  if (strcmp (newurl, oldurl) != 0)
    {
      forkplayer (newurl);
      strcpy (oldurl, newurl);
    }
}

void
Feidian::onbuttonsaveclicked ()
{
  QSettings settings;
  settings.writeEntry ("/feidian/player", editplayer->text ());
}

void
Feidian::onbuttonplayerclicked ()
{
  killplayer ();
  forkplayer (newurl);
}

void
Feidian::onbuttonquitclicked ()
{
  if (this->running ())
    {
      this->terminate ();
      this->wait ();
    }
  killplayer ();
}

void
Feidian::onbuttonattachclicked ()
{
  traced_process = find_pid_by_name ("Win32Client.exe", "-r");
  if (traced_process == 0)
    bartip->message ("err: no process");
  else
    {
      if (!this->running ())
	{
	  bartip->message ("attached");
	  this->start ();
	}
    }
}

void
Feidian::onbuttondetachclicked ()
{
  if (this->running ())
    {
      this->terminate ();
      this->wait ();
      bartip->clear ();
      textstream->clear ();
    }
}

void
Feidian::onplayerexit ()
{
  buttonplayer->setPaletteForegroundColor (Qt::black);
}

void
Feidian::forkplayer (char *url)
{
  player->clearArguments ();
  QStringList strlst = QStringList::split (" ", editplayer->text ());
  for (QStringList::Iterator it = strlst.begin (); it != strlst.end (); ++it)
    {
      player->addArgument (*it);
    }
  player->addArgument (url);
  player->start ();
  buttonplayer->setPaletteForegroundColor (Qt::blue);
}

void
Feidian::killplayer ()
{
  player->tryTerminate ();
}


void
Feidian::run ()
{
  long orig_eax;
  long params[2];
  int status;
  char *str;
  char *strtok, *strtok_buf;
  int toggle = 0;

  ::ptrace ((enum __ptrace_request) PTRACE_ATTACH, traced_process, NULL,
	    NULL);

  while (1)
    {
      if (::wait (&status) == -1)
	break;
      ////status=1407:Trace Trap, 4991:Stop, 3455:Broken Pipe

      if (WIFSTOPPED (status))
	{
	  switch (WSTOPSIG (status))
	    {
	    case SIGSTOP:

	    case SIGTRAP:

	      orig_eax =::ptrace ((enum __ptrace_request) PTRACE_PEEKUSER,
				  traced_process, 4 * ORIG_EAX, NULL);
	      if (orig_eax == SYS_write)
		{
		  if (toggle == 0)
		    {
		      toggle = 1;
		      params[0] =::
			ptrace ((enum __ptrace_request) PTRACE_PEEKUSER,
				traced_process, 4 * ECX, NULL);
		      params[1] =::
			ptrace ((enum __ptrace_request) PTRACE_PEEKUSER,
				traced_process, 4 * EDX, NULL);
		      str =
			(char *)::malloc ((params[1] + 1) * sizeof (char));
		      getdata (traced_process, params[0], str, params[1]);

		      if (strstr (str, "@LM_") != 0)
			{
			  strtok = strstr (strstr (str, "@LM_") + 1, "@") + 1;
			  textstream->append (g_pCodec->toUnicode (strtok));

			  if (strstr (str, "@try to connect...") != 0)
			    bartip->message ("try to connect...");

			  if (strstr (str, "@Resource Not Found!") != 0)
			    bartip->message ("Resource Not Found!");
			}
		      else if (strstr (str, "http:") != 0)
			{
			  textstream->append (g_pCodec->toUnicode (str));

			  ////strtok_r will change data!
			  strtok =
			    strtok_r (strstr (str, "http:"), "\">",
				      &strtok_buf);
			  strcpy (newurl, strtok);

			  strtok =
			    strtok_r (strstr (str, "<TITLE>") + 7, "<",
				      &strtok_buf);
			  bartip->message (g_pCodec->toUnicode (strtok));
			}
		      ::free (str);
		    }
		  else
		    {
		      toggle = 0;
		    }
		}
	    default:		////SIGPIPE
	      ::ptrace ((enum __ptrace_request) PTRACE_SYSCALL,
			traced_process, NULL, NULL);
	    }
	}
      else if (WIFEXITED (status))
	{
	  buttonplayer->setPaletteForegroundColor (Qt::black);
	}
    }
  ////quit
  ::ptrace ((enum __ptrace_request) PTRACE_DETACH, traced_process, NULL,
	    NULL);
  printf ("thread exited\n");
  bartip->clear ();
  textstream->clear ();
}

void
Feidian::getdata (pid_t traced_process, long addr, char *str, int len)
{
  char *laddr;
  int i, j;
  union u
  {
    long val;
    char chars[sizeof (long)];
  } data;
  i = 0;
  j = len / sizeof (long);
  laddr = str;
  while (i < j)
    {
      data.val =::ptrace ((enum __ptrace_request) PTRACE_PEEKDATA,
			  traced_process, addr + i * 4, NULL);
      ::memcpy (laddr, data.chars, sizeof (long));
      ++i;
      laddr += sizeof (long);
    }
  j = len % sizeof (long);
  if (j != 0)
    {
      data.val =::ptrace ((enum __ptrace_request) PTRACE_PEEKDATA,
			  traced_process, addr + i * 4, NULL);
      ::memcpy (laddr, data.chars, j);

    }
  str[len] = '\0';
}

#define READ_BUF_SIZE 256

long
Feidian::find_pid_by_name (char *pidName, char *pidName2)
{
  DIR *dir;
  struct dirent *next;

  dir =::opendir ("/proc");
  if (!dir)
    {
      ::fprintf (stderr, "Cannot open /proc\n");
      ::exit (1);
    }

  while ((next =::readdir (dir)) != NULL)
    {
      FILE *status;
      char filename[READ_BUF_SIZE];
      char buffer[READ_BUF_SIZE];

      if (::strcmp (next->d_name, "..") == 0)
	continue;

      if (!::isdigit (*next->d_name))
	continue;

      ::sprintf (filename, "/proc/%s/cmdline", next->d_name);
      if (!(status =::fopen (filename, "r")))
	{
	  continue;
	}

      int n;
      if ((n =::fread (buffer, 1, sizeof buffer, status)) == 0)
	{
	  fclose (status);
	  continue;
	}
      fclose (status);

      int red = 0;

      while (red < n)
	{
	  if (strcmp (buffer + red, pidName) == 0)
	    {
	      red += strlen (buffer + red) + 1;
	      if (red >= n)
		break;
	      if (strcmp (buffer + red, pidName2) == 0)
		return strtol (next->d_name, NULL, 0);
	    }
	  red += strlen (buffer + red) + 1;
	}

    }
  return 0;
}

int
Feidian::sound_read (void)
{
  int result;
  if (::ioctl (sound_handle, MIXER_READ (4), &result) == -1)
    {
      perror ("read_error");
      return -1;
    }
  result = ((result >> 8) + (result & 0xFF)) >> 1;
  result = (result > 0) ? result : 0;
  result = (result < 100) ? result : 100;
  return result;
}

void
Feidian::sound_write (int leftright)
{
  leftright = (leftright < 0) ? 0 : leftright;
  leftright = (leftright > 100) ? 100 : leftright;
  leftright = (leftright << 8) | (leftright & 0xFF);
  if (::ioctl (sound_handle, MIXER_WRITE (4), &leftright) == -1)
    {
      perror ("write_error");
      return;
    }
}


int
main (int argc, char **argv)
{
  QApplication a (argc, argv);
  Feidian w;
  QObject::
    connect (&a, SIGNAL (aboutToQuit ()), &w, SLOT (onbuttonquitclicked ()));
  w.resize (350, 300);
  a.setMainWidget (&w);
  w.show ();
  return a.exec ();
}

#include "main.moc"
